package br.ufu.comp.pdi;

import java.awt.image.*;
import javax.imageio.*;
import java.io.*;
import java.util.*;

public class ImageData{
    public static final int RGB = 0;
	public static final int CINZA = 1;
	public static final int BINARIO = 2;
	//dados da imagem
	private ArrayList<int[][]> canaisImagem;
	private int altura, largura, nro_canais, tipo_dado;
	
	public ImageData(int[][] canal) {
		commonBuilder(canal[0].length, canal.length, 1, CINZA);
		canaisImagem.add(canal);
	}
	
	public ImageData(int altura, int largura, int nro_canais, int tipo_dado){
		commonBuilder(altura, largura, nro_canais, tipo_dado);
		for(int z = 0; z < nro_canais; z++ ){
			int[][] ca = new int[altura][largura];
			canaisImagem.add(z,ca);
		}
	}
	
	private void commonBuilder(int altura, int largura, int nro_canais, int tipo_dado) {
		this.largura = largura;
		this.altura = altura;
		this.nro_canais = nro_canais;
		this.tipo_dado = tipo_dado;
		canaisImagem = new ArrayList<>();
	}
	
	public ImageData(List<int[][]> canais, int tipo_dado) throws Exception {
		
		if(!check(canais)) {
			throw new Exception("Tamanhos diferentes");
		}
		
		commonBuilder(canais.get(0).length, canais.get(0)[0].length, canais.size(), tipo_dado);
		
		for(int[][] canal : canais) {
			canaisImagem.add(canal);
		}
		
	}
	
	public boolean check(List<int[][]> canais) {
		return true; // FIXME checagem de tamanho dos canais
	}
	
	public ImageData(ImageData im){
		largura = im.largura();
		altura = im.altura();
		nro_canais = im.nroCanais();
		tipo_dado = im.tipoImage();
		canaisImagem = new ArrayList<>();
		
		for(int z = 0; z < nro_canais; z++ ){
			int[][] ca = new int[altura][largura];
			for(int y = 0; y < altura; y++ ){
				for(int x = 0; x < largura; x++ ){
					ca[y][x] = im.get(y, x, z);
				}
			}
			canaisImagem.add(z,ca);
		}
	}
	
	private void processa1Canal(byte[] pixels){
		int[][] G = new int[altura][largura];
		int cont = 0;
		for(int y = 0; y < altura; y++ ) {
			for(int x = 0; x < largura; x++ ){
				G[y][x] = (int) pixels[cont] & 0xFF;
				cont++;			
			}
		}	
		
		canaisImagem = new ArrayList<>();
		canaisImagem.add(0,G);
		nro_canais = 1;
		tipo_dado = CINZA;
		isImageDataBinary();
	}
	private void processa34Canais(byte[] pixels){
		int[][] R = new int[altura][largura];
		int[][] G = new int[altura][largura];
		int[][] B = new int[altura][largura];
		
		int tamanho, cont;
		if(nro_canais == 3){
			tamanho = 3;
			cont = 0;
		}else{
			cont = 1;
			tamanho = nro_canais;
		}
		
		for(int y = 0; y < altura; y++ ) {
			for(int x = 0; x < largura; x++ ){
				B[y][x] = (int) pixels[cont] & 0xFF;
				G[y][x] = (int) pixels[cont+1] & 0xFF;
				R[y][x] = (int) pixels[cont+2] & 0xFF;
				cont += tamanho;
			}
		}	
			
		canaisImagem = new ArrayList<>();
		canaisImagem.add(0,R);
		canaisImagem.add(1,G);
		canaisImagem.add(2,B);	
		nro_canais = 3;
		tipo_dado = RGB;
		if(isImageDataGray())
			isImageDataBinary();
	}
	public ImageData(String path){
		try{
			BufferedImage image = ImageIO.read(new File(path));	
			//System.out.println("Tipo: " + image.getType());
			
			byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
			largura = image.getWidth();
			altura = image.getHeight();
			nro_canais = pixels.length / (altura * largura);
			
			//int kk = (int) pixels[1] & 0xFF;
			//System.out.println(altura+" "+largura+" "+nro_canais+" "+kk);
			
			if(nro_canais < 1){
				altura = 0;
				largura = 0;
				nro_canais = 0;
				throw new IllegalArgumentException("Arquivo inválido: " + path); 
			}
			
			if(nro_canais == 1)
				processa1Canal(pixels);
			else
				processa34Canais(pixels);
	  
		} catch (IOException e){
			System.err.println("Erro na abertura do arquivo " + path);
		  
		}
	}
	
	private boolean isImageDataGray(){
        boolean res = true;
		int[][] R = canaisImagem.get(0);
		int[][] G = canaisImagem.get(1);
		int[][] B = canaisImagem.get(2);
        for(int y = 0; y < altura; y++ ) {
			for(int x = 0; x < largura; x++ ){
                if((R[y][x] != G[y][x]) || (G[y][x] != B[y][x]) || (R[y][x] != B[y][x])){
                    res = false;
                    break;
                }                
            }
            if (res == false)
                break;
        }

        if (res == true){
            tipo_dado = CINZA;
            nro_canais = 1;
			canaisImagem.remove(2);
			canaisImagem.remove(1);         
        }
        return res;
    }
    	
	private void isImageDataBinary(){
        boolean res = true;
		int[][] G = canaisImagem.get(0);
        for(int y = 0; y < altura; y++ ) {
			for(int x = 0; x < largura; x++ ){
                if((G[y][x] != 0) && (G[y][x] != 255)){
                    res = false;
                    break;
                }                
            }
            if (res == false)
                break;
        }

        if (res == true){
            tipo_dado = BINARIO;
        }
    }
	
	public int get(int linha, int coluna, int canal){
		int[][] ca = canaisImagem.get(canal);
		return ca[linha][coluna];
	}
	public int get(int linha, int coluna){
		return get(linha,coluna,0);
	}

	public int[][] getCanal(int canal){
		return canaisImagem.get(canal);
	}
	
	public void set(int linha, int coluna, int canal, int valor){
		int[][] ca = canaisImagem.get(canal);
		ca[linha][coluna] = valor;
	}
	
	public void set(int linha, int coluna, int valor){
		set(linha,coluna,0,valor);
	}
	
	public int altura(){
		return altura;
	}
	
	public int largura(){
		return largura;
	}
	
	public int nroCanais(){
		return canaisImagem.size();
	}	
	
	public int tipoImage(){
		return tipo_dado;
	}

	public BufferedImage getBMP(){
		BufferedImage image = new BufferedImage(altura,largura,BufferedImage.TYPE_INT_ARGB);
		int[] pixels = image.getRGB(0, 0, largura, altura, null, 0, largura);
		
		if(nro_canais == 3){
			int[][] R = canaisImagem.get(0);
			int[][] G = canaisImagem.get(1);
			int[][] B = canaisImagem.get(2);
					
			int cont = 0;
			for(int y = 0; y < altura; y++ ){
				for(int x = 0; x < largura; x++ ){
					pixels[cont] = 0xFF000000 | 
								   ((R[y][x] << 16) & 0x00FF0000) | 
								   ((G[y][x] << 8)  & 0x0000FF00) | 
								   (B[y][x] & 0x000000FF);
								   
					cont++;				
				}
			}			
		}else{
			int[][] G = canaisImagem.get(0);					
			int cont = 0;
			for(int y = 0; y < altura; y++ ){
				for(int x = 0; x < largura; x++ ){
					pixels[cont] = 0xFF000000 | 
								   ((G[y][x] << 16) & 0x00FF0000) | 
								   ((G[y][x] << 8)  & 0x0000FF00) | 
								   (G[y][x] & 0x000000FF);
								   
					cont++;				
				}
			}
		}
		image.setRGB(0, 0, largura, altura, pixels, 0, largura);
		return image;
	}
	
	public static void main(String args[]){
		
		//testes...
		System.out.println("Tipo: " + ImageData.RGB);
		try{
			ImageData t = new ImageData("D:\\lena_cor.bmp");
			BufferedImage bi = t.getBMP();
			ImageIO.write(bi, "png", new File("lena_cor_saved.png"));
			
			ImageData t1 = new ImageData(".\\imagem1.png");
			BufferedImage bi1 = t1.getBMP();
			ImageIO.write(bi1, "png", new File("imagem1_saved.png"));
			
			ImageData t2 = new ImageData(".\\imagem2.png");
			BufferedImage bi2 = t2.getBMP();
			ImageIO.write(bi2, "png", new File("imagem2_saved.png"));
			
			ImageData t3 = new ImageData(".\\imagem3.png");
			BufferedImage bi3 = t3.getBMP();
			ImageIO.write(bi3, "png", new File("imagem3_saved.png"));
			
		} catch (IOException e){
			System.out.println("Erro ao salvar o arquivo");
		  
		}
		
		
			
		/*
		
		try{
			ImageData t = new ImageData(".\\lena_cor.bmp");
			ImageData t1 = new ImageData(".\\imagem1.png");
			ImageData t2 = new ImageData(".\\imagem2.png");
			BufferedImage bi = t.getBMP();
			//ImageData t1 = teste.RGB2Gray(t);
			//BufferedImage bi = t1.getBMP();
			ImageIO.write(bi, "png", new File("saved.png"));
		} catch (IOException e){
			System.out.println("Erro ao salvar o arquivo");
		  
		}
		*/
	}


}
